#pragma once 
//----------------------------------------------------------------------------------------------------------------
//			Filename:		Matte.h
//			  Author:		Adrian De Barro
//			    Date:		12/08/2015
//----------------------------------------------------------------------------------------------------------------
//			  Status: REFACTORING
//----------------------------------------------------------------------------------------------------------------
#include <vector>
#include <string>

#include "..\CoreClasses\Ray\ShadowRay\ShadowRay.h"
#include "..\BRDF\Lambertian.h"
#include "..\CoreClasses\RGBObject\RGBColor.h"
#include "..\Material\Material.h"
#include "..\Light\Light.h"
#include "..\Shadows\ShadowTester.h"


//namespace Engine
//{
//	class Tracer
//	{
//		virtual bool TestShadow(ShadowRay, double);
//	};
//}


namespace Engine
{
	//----------------------------------------------------------------------------------------------------------------
	//			Class Name: Matte
	//		   Description: Concrete implementation of Materials abstract class
	//----------------------------------------------------------------------------------------------------------------
	//			    Status: REFACTORING
	//----------------------------------------------------------------------------------------------------------------
	class Matte : public Engine::Material
	{
	public:
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		Matte() : ambientBrdf(new Lambertian), diffuseBrdf(new Lambertian)
		{}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		Matte(float p_kd, RGBColor p_color) : ambientBrdf(new Lambertian), diffuseBrdf(new Lambertian)
		{
			SetCd(p_color);
			SetKd(p_kd);
			m_containsTexture = false;
			m_materialTexture = new SolidColor(p_color);
			diffuseBrdf = new Lambertian(p_kd, m_materialTexture);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: string materialName, float constant, RGBColor color
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		Matte(std::string p_materialName,float p_kd, RGBColor p_color) : ambientBrdf(new Lambertian)
		{
			
			m_materialTexture = new SolidColor(p_color);
			diffuseBrdf = new Lambertian(p_kd, m_materialTexture);
			SetCd(p_color);
			m_containsTexture = false;
			SetKd(p_kd);
			SetMaterialName(p_materialName);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: string materialName, float constant, RGBColor color
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		Matte(std::string p_materialName, float p_kd, std::string p_texturePath) : ambientBrdf(new Lambertian)
		{
			m_containsTexture = true;
			m_materialTexture = new ImageTexture(p_texturePath);
			diffuseBrdf = new Lambertian(p_kd, m_materialTexture);
			SetKd(p_kd);
			SetMaterialName(p_materialName);
			SetType(DIFF);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: string materialName, float constant, string texturePath, RGBColor
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		Matte(std::string p_materialName, float p_kd, std::string p_texturePath, RGBColor p_secondaryColor) : ambientBrdf(new Lambertian)
		{
			m_containsTexture = true;
			m_materialTexture = new ImageTexture(p_texturePath, p_secondaryColor);
			diffuseBrdf = new Lambertian(p_kd, m_materialTexture);
			SetKd(p_kd);
			SetMaterialName(p_materialName);
			SetType(DIFF);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: SetKa
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void SetKa(float p_kA)
		{
			m_kA = p_kA;
			ambientBrdf->SetKd(p_kA);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: SetKd
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void SetKd(float p_kD)
		{
			diffuseBrdf->SetKd(p_kD);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: SetCd
		//		Return Value: VOID
		//		  Parameters: RGBColor color
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void SetCd(RGBColor p_color)
		{
			diffuseBrdf->SetColor(p_color);
			ambientBrdf->SetColor(p_color);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetMatteColor
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		RGBColor GetMatteColor(void)
		{
			return diffuseBrdf->GetColor();
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: Shade
		//		Return Value: VOID
		//		  Parameters: Collision Detail, vector<Light*>&, Tracer*, Light* ambientLight (Optional)
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		using Engine::Material::Shade;
		RGBColor Shade(ShadeRec& sr, std::vector<Light*>& sceneLights, Tracer* p_tracerPtr, RGBColor& p_colorAtPoint, Light* p_ambientLight = NULL)
		{
			Point3D w0 = -sr.ray.d;
			//ShadowTester testForShadow(p_tracerPtr, sceneLights);

			RGBColor outputColor = RGBColor(0);

			if (p_ambientLight != NULL)
			{
				outputColor = ambientBrdf->rho(sr, w0) * p_ambientLight->L(sr);
			}

			int sizeLights = sceneLights.size();
			Point3D newOrigin = sr.localHitPoint + (kEpsilon * sr.GetNormal());
			for (int lightIndex = 0; lightIndex < sizeLights; lightIndex++)
			{
				Point3D wi = sceneLights[lightIndex]->GetDirection(sr);
				wi.normalize();
				float n_dot_wi = DOT(sr.normal, wi);
				bool shadowed = true;

				if (sceneLights[lightIndex]->GetLightType() == AMBIENT_LIGHTING)
				{
					RGBColor diffuseBrdfComponent = diffuseBrdf->f(sr, wi, w0, p_colorAtPoint);
					RGBColor sceneLightComponent = sceneLights[lightIndex]->L(sr);

					RGBColor diffuseLightAddition = diffuseBrdfComponent * sceneLightComponent;
					outputColor = outputColor + diffuseLightAddition;
				}
				else
				{

					if (sceneLights[lightIndex]->GetLightType() == POINT_LIGHT)
					{
						Point3D directionToLight = (sceneLights[lightIndex]->GetLocation() - sr.localHitPoint);
						double distanceToLight = directionToLight.length();
						Point3D lightDirection = directionToLight.normalize();
						Point3D kEpsillonCollisionPoint = sr.GetkEpsillonCollisionPoint(lightDirection);

						ShadowRay shadowRay(kEpsillonCollisionPoint, lightDirection, Common::kEpsilon, (distanceToLight - Common::kEpsilon));
						//cout << shadowRay.GetTmax() << endl;
						//how to deal with area ligths
						shadowed = p_tracerPtr->TestShadow(shadowRay);
					}
					else if (sceneLights[lightIndex]->GetLightType() == DIRECTIONAL_LIGHT)
					{
						Point3D directionToLight = (sceneLights[lightIndex]->GetLocation())*-1;
						Point3D lightDirection = directionToLight.normalize();

						Point3D kEpsillonCollisionPoint = sr.GetkEpsillonCollisionPoint(lightDirection);

						ShadowRay shadowRay(kEpsillonCollisionPoint, directionToLight, Common::kEpsilon, 1000000);
						shadowed = p_tracerPtr->TestShadow(shadowRay);
					}

					if (n_dot_wi > 0 && !shadowed)
						//if (n_dot_wi > 0)
					{
						//ShadowRay shadowRay(newOrigin, wi);
						ShadeRec tempDetails;
						/*bool intersection = testForShadow.TestShadowRay(shadowRay, sceneLights[lightIndex]);
						if (!intersection)
						{*/
						if (!this->m_containsTexture)
						{
							RGBColor diffuseBrdfComponent = diffuseBrdf->f(sr, wi, w0, p_colorAtPoint);
							RGBColor sceneLightComponent = sceneLights[lightIndex]->L(sr)* n_dot_wi;

							RGBColor diffuseLightAddition = diffuseBrdfComponent * sceneLightComponent;
							outputColor = outputColor + diffuseLightAddition;
						}
						else
						{
							RGBColor diffuseBrdfComponent = diffuseBrdf->f(sr, wi, w0, p_colorAtPoint, true);
							RGBColor sceneLightComponent = sceneLights[lightIndex]->L(sr)* n_dot_wi;
							RGBColor diffuseLightAddition = diffuseBrdfComponent * sceneLightComponent;
							outputColor = outputColor + diffuseLightAddition;
						}
					}
					else
					{
						if (!this->m_containsTexture)
						{
							RGBColor diffuseBrdfComponent = diffuseBrdf->f(sr, wi, w0, p_colorAtPoint);
						}
						else
						{
							RGBColor diffuseBrdfComponent = diffuseBrdf->f(sr, wi, w0, p_colorAtPoint, true);
						}
					}
				}
			}
			return outputColor;
		}

	private:
		float m_kA;
		float m_kD;
		
		Lambertian *ambientBrdf;
		Lambertian *diffuseBrdf;
	};
	//----------------------------------------------------------------------------------------------------------------
}
//----------------------------------------------------------------------------------------------------------------
